using System;

using Org.BouncyCastle.Crypto.Parameters;

namespace Org.BouncyCastle.Crypto.Engines
{
	public class RC4Engine
		: IStreamCipher
	{
		private readonly static int STATE_LENGTH = 256;

		/*
		* variables to hold the state of the RC4 engine
		* during encryption and decryption
		*/

		private byte[]	engineState;
		private int		x;
		private int		y;
		private byte[]	workingKey;

		/**
		* initialise a RC4 cipher.
		*
		* @param forEncryption whether or not we are for encryption.
		* @param parameters the parameters required to set up the cipher.
		* @exception ArgumentException if the parameters argument is
		* inappropriate.
		*/
		public void Init(
			bool				forEncryption,
			ICipherParameters	parameters)
		{
			if (parameters is KeyParameter)
			{
				/*
				* RC4 encryption and decryption is completely
				* symmetrical, so the 'forEncryption' is
				* irrelevant.
				*/
				workingKey = ((KeyParameter)parameters).GetKey();
				SetKey(workingKey);

				return;
			}

			throw new ArgumentException("invalid parameter passed to RC4 init - " + parameters.GetType().ToString());
		}

		public string AlgorithmName
		{
			get { return "RC4"; }
		}

		public byte ReturnByte(
			byte input)
		{
			x = (x + 1) & 0xff;
			y = (engineState[x] + y) & 0xff;

			// swap
			byte tmp = engineState[x];
			engineState[x] = engineState[y];
			engineState[y] = tmp;

			// xor
			return (byte)(input ^ engineState[(engineState[x] + engineState[y]) & 0xff]);
		}

		public void ProcessBytes(
			byte[]	input,
			int		inOff,
			int		length,
			byte[]	output,
			int		outOff
		)
		{
			if ((inOff + length) > input.Length)
			{
				throw new DataLengthException("input buffer too short");
			}

			if ((outOff + length) > output.Length)
			{
				throw new DataLengthException("output buffer too short");
			}

			for (int i = 0; i < length ; i++)
			{
				x = (x + 1) & 0xff;
				y = (engineState[x] + y) & 0xff;

				// swap
				byte tmp = engineState[x];
				engineState[x] = engineState[y];
				engineState[y] = tmp;

				// xor
				output[i+outOff] = (byte)(input[i + inOff]
						^ engineState[(engineState[x] + engineState[y]) & 0xff]);
			}
		}

		public void Reset()
		{
			SetKey(workingKey);
		}

		// Private implementation

		private void SetKey(
			byte[] keyBytes)
		{
			workingKey = keyBytes;

			// System.out.println("the key length is ; "+ workingKey.Length);

			x = 0;
			y = 0;

			if (engineState == null)
			{
				engineState = new byte[STATE_LENGTH];
			}

			// reset the state of the engine
			for (int i=0; i < STATE_LENGTH; i++)
			{
				engineState[i] = (byte)i;
			}

			int i1 = 0;
			int i2 = 0;

			for (int i=0; i < STATE_LENGTH; i++)
			{
				i2 = ((keyBytes[i1] & 0xff) + engineState[i] + i2) & 0xff;
				// do the byte-swap inline
				byte tmp = engineState[i];
				engineState[i] = engineState[i2];
				engineState[i2] = tmp;
				i1 = (i1+1) % keyBytes.Length;
			}
		}
	}

}
